package com.youlu.campus.admin.auth.service.impl;

import com.alibaba.fastjson.JSON;
import com.mongodb.client.result.UpdateResult;
import com.youlu.campus.admin.auth.entity.Menu;
import com.youlu.campus.admin.auth.entity.SystemResource;
import com.youlu.campus.admin.auth.entity.SystemRoleResouce;
import com.youlu.campus.admin.auth.entity.SystemUser;
import com.youlu.campus.admin.auth.entity.exception.AuthErrorCode;
import com.youlu.campus.admin.auth.service.ResourceService;
import com.youlu.campus.admin.auth.service.RoleService;
import com.youlu.campus.admin.auth.service.UserService;
import com.youlu.campus.admin.auth.vo.MenuVo;
import com.youlu.campus.admin.common.LoggerService;
import com.youlu.campus.common.exception.BusinessException;
import com.youlu.campus.entity.VO.req.SystemResourceQueryVO;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.repository.support.PageableExecutionUtils;
import org.springframework.stereotype.Service;

import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * Created by zhuhuaiqi
 */
@Service("resourceService")
public class ResourceServiceImpl extends LoggerService implements ResourceService {

    public static final int QUERY_IN_MAX_SIZE = 100;

    public static final String RESOURCE_PARENT_SPLIT = "/";

    @Autowired
    RoleService roleService;
    @Autowired
    MongoTemplate mongoTemplate;
    @Autowired
    UserService userService;

    @Override
    public List<SystemResource> listByRoleIds(String tenantId, List<String> roleIds) {
        logger.debug("listByRoleIds roleIds:{}", JSON.toJSONString(roleIds));
        if (CollectionUtils.isEmpty(roleIds) || StringUtils.isBlank(tenantId)) {
            return Collections.EMPTY_LIST;
        }
        Query query = new Query();
        query.addCriteria(Criteria.where("tenantId").is(tenantId).and("roleId").in(roleIds));
        List<SystemRoleResouce> roleResouces = mongoTemplate.find(query, SystemRoleResouce.class);
        if (CollectionUtils.isEmpty(roleResouces)) {
            return Collections.EMPTY_LIST;
        }
        List<String> menuResourceIds = roleResouces.stream().map(SystemRoleResouce::getResourceId).distinct().collect(Collectors.toList());
        logger.debug("listMenusByRoleId menuResourceIds:{}", JSON.toJSONString(menuResourceIds));
        List<SystemResource> resourceList = findByInIds(menuResourceIds);
        logger.info("listMenusByRoleId resourceList:{}", JSON.toJSONString(resourceList));
        return resourceList;
    }

    @Override
    public List<Menu> listMenus(String userId, String tenantId) {
        logger.debug("listMenus userId:{}, comId:{}", userId, tenantId);
        List<SystemResource> resourcesList = listByComIdAndUserId(userId, tenantId);
        List<Menu> menus = treeMenus("0", resourcesList);
        logger.debug("listMenus db user:{}, comId:{}, menus:{}", userId, tenantId, JSON.toJSONString(menus));
        return menus;
    }

    @Override
    public List<Menu> listMenusByRoleId(String roleId, String tenantId) {
        logger.debug("listMenusByRoleId roleId:{}, comId:{}", roleId, tenantId);
        Query query = new Query();
        query.addCriteria(Criteria.where("tenantId").is(tenantId).and("roleId").is(roleId));
        List<SystemRoleResouce> datas = mongoTemplate.find(query, SystemRoleResouce.class);
        if (CollectionUtils.isEmpty(datas)) {
            return Arrays.asList();
        }
        List<String> menuResourceIds = datas.stream().map(SystemRoleResouce::getResourceId).distinct().collect(Collectors.toList());
        logger.debug("listMenusByRoleId menuResourceIds:{}", JSON.toJSONString(menuResourceIds));
        List<SystemResource> resourceList = findByInIds(menuResourceIds);
        List<Menu> menus = treeMenus("0", resourceList);
        logger.debug("listMenus db roleId:{}, comId:{}, menus:{}", roleId, tenantId, JSON.toJSONString(menus));
        return menus;
    }

    /**
     * 根据id 查询
     *
     * @param menuResourceIds
     * @return
     */
    private List<SystemResource> findByInIds(List<String> menuResourceIds) {
        List<SystemResource> resourceList = new ArrayList<>(menuResourceIds.size());
        for (int i = 0; i * QUERY_IN_MAX_SIZE < menuResourceIds.size(); i++) {
            int indexStart = i * QUERY_IN_MAX_SIZE;
            int indexEnd = (i + 1) * QUERY_IN_MAX_SIZE;
            if (indexEnd > menuResourceIds.size()) {
                indexEnd = menuResourceIds.size();
            }
            List<String> inId = menuResourceIds.subList(indexStart, indexEnd);
            //获取这下菜单
            Query menuQuery = new Query();
            menuQuery.addCriteria(Criteria.where("id").in(inId));
            List<SystemResource> partialResourceList = mongoTemplate.find(menuQuery, SystemResource.class);
            resourceList.addAll(partialResourceList);
        }
        return resourceList;
    }


    @Override
    public List<Menu> getAllMenus(String tenantId) {
        logger.debug("getAllMenus 开始获取所有菜单:{}", tenantId);
        Query query = new Query();
        query.addCriteria(Criteria.where("tenantId").is(tenantId));
        query.addCriteria(Criteria.where("type").in(2, 3));
        List<SystemResource> resourceList = mongoTemplate.find(query, SystemResource.class);
        logger.debug("getAllMenus listMenuResources resourceList:{}", JSON.toJSONString(resourceList));
        if (CollectionUtils.isEmpty(resourceList)) {
            return null;
        }
        resourceList = resourceList.stream().distinct().collect(Collectors.toList());
        List<Menu> menus = treeMenus("0", resourceList);
        logger.debug("listMenus db comId:{}, menus:{}", tenantId, JSON.toJSONString
                (menus));
        return menus;
    }

    /**
     * 菜单转换为 树结构
     *
     * @param parentId     父id
     * @param resourceList
     * @return
     */
    private List<Menu> treeMenus(String parentId, List<SystemResource> resourceList) {
        List<SystemResource> resources = resourceList.stream().filter(item -> item.getParentId().equals(parentId)).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(resources)) {
            return Arrays.asList();
        }
        List<Menu> menuList = new ArrayList<>(resources.size());
        for (SystemResource resource : resources) {
            Menu menu = new Menu();
            menu.setId(resource.getId());
            menu.setName(resource.getName());
            menu.setUrl(resource.getUrl());
            menu.setImg(resource.getImg());
            menu.setImgActive(resource.getImgActive());
            menu.setWeight(resource.getWeight());

            List<Menu> childs = treeMenus(resource.getId(), resourceList);
            menu.setChilds(childs);
            menuList.add(menu);
        }
        return menuList;
    }

    @Override
    public List<SystemResource> listByComIdAndUserId(String userId, String tenantId) {
        logger.debug("listByComIdAndUserId userId:{},tenantId:{}", userId, tenantId);
        //获取角色id 集合
        List<String> roleIds = roleService.listRoleByUserIdAndComId(userId, tenantId);
        logger.debug("listByComIdAndUserId roleService roleIds:{}", JSON.toJSONString(roleIds));
        if (CollectionUtils.isEmpty(roleIds)) {
            return Arrays.asList();
        }
        // TODO：计算用户角色
        List<SystemResource> resources = this.listByRoleIds(tenantId, roleIds);
        return resources;
    }


    @Override
    public boolean createMenu(MenuVo req) {
        logger.info("开始新增菜单:{}", JSON.toJSONString(req));
        if (java.util.Objects.isNull(req) || StringUtils.isBlank(req.getName()) || StringUtils.isBlank(req.getPermission()) ||
                java.util.Objects.isNull(req.getWeight()) || java.util.Objects.isNull(req.getType()) || StringUtils.isBlank(req.getTenantId()) ||
                StringUtils.isBlank(req.getParentId())) {
            logger.error("创建菜单参数错误");
            throw new BusinessException("名字,权限,权重,租户ID不能为空");
        }
        SystemResource insert = new SystemResource();
        BeanUtils.copyProperties(req, insert);
        insert.setId(null);
        insert.setCreatedTime(new Date());
        mongoTemplate.insert(insert);
        return true;
    }

    @Override
    public boolean createResource(SystemResource systemResource) {
        logger.info("开始新增资源:{}", JSON.toJSONString(systemResource));
        if (null == systemResource || StringUtils.isBlank(systemResource.getName()) ||
                StringUtils.isBlank(systemResource.getTenantId()) ||
                null == systemResource.getType() || null == systemResource.getRootNode()) {
            logger.error("系统参数错误:");
            throw new BusinessException(AuthErrorCode.PARAMETER_ERROR);
        }
        systemResource.setCreatedTime(new Date());
        if (Boolean.FALSE.equals(systemResource.getRootNode())) {
            if (StringUtils.isBlank(systemResource.getParentId())) {
                logger.error("资源:{} 非根节点,父节点不能为空", systemResource.getName());
                throw new BusinessException(AuthErrorCode.PARAMETER_ERROR);
            }
            Query query = new Query();
            query.addCriteria(Criteria.where("tenantId").is(systemResource.getTenantId()).and("id").is(systemResource.getParentId()));
            boolean exist = mongoTemplate.exists(query, SystemResource.class);
            if (!exist) {
                logger.error("资源:{} 不存在", systemResource.getParentId());
                throw new BusinessException(AuthErrorCode.DATA_NOT_EXIST);
            }
        } else {
            systemResource.setParentId("0");
        }
        /**
         * 资源类型：1、URL，2、DATA，3、MENU
         */
        switch (systemResource.getType()) {
            case 1:
                if (StringUtils.isBlank(systemResource.getUrl())) {
                    logger.error("资源:{} 为URL,URL不能为空", systemResource.getName());
                    throw new BusinessException(AuthErrorCode.PARAMETER_ERROR);
                }
                break;
            case 2:
                break;
            case 3:
                break;
            default:
                break;
        }
        this.setParantName(systemResource);
        SystemResource inserted = mongoTemplate.insert(systemResource);
        logger.info("新增的资源:{}", JSON.toJSONString(inserted));
        return true;

    }

    @Override
    public boolean hasPermisson(String userId, String uri) {
        SystemUser systemUser = userService.get(userId);
        if (systemUser == null) {
            logger.error("用户:{} 不存在", userId);
            return false;
        }
        List<SystemResource> resourcesList = listByComIdAndUserId(userId, systemUser.getTenantId());
        if (CollectionUtils.isEmpty(resourcesList)) {
            logger.error("resourcesList :{} 不存在", userId);
            return false;
        }
        List<String> menuIds = resourcesList.stream().map(SystemResource::getId).collect(Collectors.toList());
        logger.info("菜单的Ids:{}", JSON.toJSONString(menuIds));
        Query query = new Query();
        // 查询菜单下的URL配置
        query.addCriteria(Criteria.where("parentId").in(menuIds).and("type").is(1));
        List<SystemResource> resources = mongoTemplate.find(query, SystemResource.class);
        for (SystemResource systemResource : resources) {
            if (StringUtils.isBlank(systemResource.getUrl())) {
                logger.error("资源:{} URL为空,跳过", systemResource.getName());
                continue;
            }
            String url = systemResource.getUrl();
            if (url.contains("/**")) {
                url = url.replace("/**", "");
            }
            if (uri.contains(url)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean hasButtonPermission(String userId, String permission) {
        return false;
    }

    @Override
    public List<SystemResource> listByType(String tenantId, Integer type) {
        logger.info(";>>> 开始获取资源:{},{}", tenantId, type);
        Query query1 = new Query().addCriteria(Criteria.where("type").is(type));
        List<SystemResource> datas = mongoTemplate.find(query1, SystemResource.class);
        logger.info("资源类型:{} 数据:{}", type, datas == null ? 0 : datas.size());
        return datas;
    }

    @Override
    public boolean updateResource(@NotNull SystemResource systemResource) {
        if (Boolean.TRUE.equals(systemResource.getRootNode())) {
            systemResource.setParentId("0");
        }
        Query query = new Query();
        query.addCriteria(Criteria.where("id").is(systemResource.getId()));
        Update update = new Update();
        boolean toUpdate = false;
        if (StringUtils.isNotBlank(systemResource.getName())) {
            update.set("name", systemResource.getName());
            toUpdate = true;
        }
        if (StringUtils.isNotBlank(systemResource.getPermission())) {
            update.set("permission", systemResource.getPermission());
            toUpdate = true;
        }
        if (java.util.Objects.nonNull(systemResource.getType())) {
            update.set("type", systemResource.getType());
            toUpdate = true;
        }
        if (StringUtils.isNotBlank(systemResource.getUrl())) {
            update.set("url", systemResource.getUrl());
            toUpdate = true;
        }
        if (StringUtils.isNotBlank(systemResource.getImg())) {
            update.set("img", systemResource.getImg());
            toUpdate = true;
        }
        if (StringUtils.isNotBlank(systemResource.getImgActive())) {
            update.set("imgActive", systemResource.getImgActive());
            toUpdate = true;
        }
        if (java.util.Objects.nonNull(systemResource.getWeight())) {
            update.set("weight", systemResource.getWeight());
            toUpdate = true;
        }
        if (StringUtils.isNotBlank(systemResource.getParentId())) {
            update.set("parentId", systemResource.getParentId());
            this.setParantName(systemResource);
            update.set("parentName", systemResource.getParentName());
            toUpdate = true;
        }
        if (java.util.Objects.nonNull(systemResource.getRootNode())) {
            update.set("rootNode", systemResource.getRootNode());
            toUpdate = true;
        }
        if (toUpdate) {
            update.set("updatedTime", new Date());
            UpdateResult updateResult = mongoTemplate.updateFirst(query, update, SystemResource.class);
            return updateResult.getModifiedCount() > 0 ? true : false;
        }
        return true;
    }

    @Override
    public SystemResource findOne(String id) {
        Query query = new Query();
        query.addCriteria(Criteria.where("id").is(id));
        SystemResource s = mongoTemplate.findOne(query, SystemResource.class);
        if (null != s) {
            this.setParantName(s);
        }
        return s;
    }

    @Override
    public boolean delete(String id) {
        Query query = new Query();
        query.addCriteria(Criteria.where("id").is(id));
        Update update = new Update();
        update.set("deleted", true);
        update.set("updatedTime", new Date());
        UpdateResult updateResult = mongoTemplate.updateFirst(query, update, SystemResource.class);
        return updateResult.getModifiedCount() > 0 ? true : false;
    }

    @Override
    public Page<SystemResource> list(SystemResourceQueryVO req) {
        Query query = new Query();
        query.addCriteria(Criteria.where("deleted").ne(Boolean.TRUE));
        if (StringUtils.isNotBlank(req.getName())) {
            Pattern pattern = Pattern.compile("^.*" + req.getName() + ".*$", Pattern.CASE_INSENSITIVE);
            query.addCriteria(Criteria.where("name").regex(pattern));
        }
        if (null != req.getType()) {
            query.addCriteria(Criteria.where("type").is(req.getType()));
        }
        if (StringUtils.isNotBlank(req.getParentId())) {
            query.addCriteria(Criteria.where("parentId").is(req.getParentId()));
        }
        if (null != req.getRootNode()) {
            query.addCriteria(Criteria.where("rootNode").is(req.getRootNode()));
        }
        long count = this.mongoTemplate.count(query, SystemResource.class);
        PageRequest pageRequest = PageRequest.of(req.getPage() - 1, req.getPageSize());
        if (count == 0) {
            return PageableExecutionUtils.getPage(new ArrayList<>(), pageRequest, () -> count);
        }
//        query.with(new Sort(Sort.Direction.DESC, "createdTime"));
        Sort sort = Sort.by(Sort.Direction.DESC, "createdTime");
        query.with(sort);
        query.with(pageRequest);
        List<SystemResource> list = this.mongoTemplate.find(query, SystemResource.class);
        for (SystemResource s : list) {
            this.setParantName(s);
        }
        return PageableExecutionUtils.getPage(list, pageRequest, () -> count);
    }

    private void setParantName(SystemResource s) {
        if (StringUtils.isNotBlank(s.getParentId())
                && !s.getParentId().equals("0")) {
            SystemResource p = this.findOne(s.getParentId());
            if (null != p) {
                s.setParentName(p.getName());
            }
        }
    }
}
